<?php


namespace EchoPHP\Http\Response;


use EchoPHP\File\File;
use EchoPHP\File\Exception\FileException;
use EchoPHP\Http\Request;
use EchoPHP\Http\Response;

class BinaryFileResponse extends Response {
    protected static $trustXSendfileTypeHeader = false;

    /**
     * 文件类
     * @var File
     */
    protected $file;
    protected $offset = 0;
    protected $maxlen = -1;
    /**
     * 文件发送后是否直接删除
     * @var bool
     */
    protected $deleteFileAfterSend = false;


    public function __construct($file, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) {
        parent::__construct(null, $status, $headers);

        $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified);
        if ($public) {
            $this->setPublic();
        }
    }

    /**
     * 创建实例
     * @param string $file
     * @param int $status
     * @param array $headers
     * @param bool $public
     * @param string|null $contentDisposition
     * @param bool $autoEtag
     * @param bool $autoLastModified
     * @return Response|BinaryFileResponse
     */
//    public static function create($file, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) {
//        return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified);
//    }

    /**
     * 将文件转换为文件流
     * @param $file     \SplFileInfo实例或者文件路径名
     * @param string|null $contentDisposition
     * @param bool $autoEtag
     * @param bool $autoLastModified
     * @return $this
     * @throws \Exception
     */
    public function setFile($file, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) {
        if (!$file instanceof File) {
            if ($file instanceof \SplFileInfo) {
                $file = new File($file->getPathname());
            } else {
                $file = new File((string)$file);
            }
        }

        if (!$file->isReadable()) {
            throw new FileException('只能操作可读文件，请确认文件权限[' . $file->getPathname() . ']');
        }

        $this->file = $file;

        if ($autoEtag) {
            $this->setAutoEtag();
        }

        if ($autoLastModified) {
            $this->setAutoLastModified();
        }

        if ($contentDisposition) {
            $this->setContentDisposition($contentDisposition);
        }

        return $this;
    }

    /**
     * 根据文件名自动设置Etag
     * @return $this
     */
    public function setAutoEtag() {
        $this->setEtag(base64_encode(hash_file('sha256', $this->file->getPathname(), true)));

        return $this;
    }

    /**
     * 根据文件自动设置文件最后修改时间
     * @return $this
     */
    public function setAutoLastModified() {
        $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime()));

        return $this;
    }

    /**
     * 根据给定的文件名设置Content-Disposition头信息
     *
     * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT
     * @param string $filename 用于替代实际文件名的自定义名称
     * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename
     *
     * @return $this
     */
    public function setContentDisposition($disposition, $filename = '', $filenameFallback = '') {
        if ('' === $filename) {
            $filename = $this->file->getFilename();
        }

        if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) {
            $encoding = mb_detect_encoding($filename, null, true) ?: '8bit';

            for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) {
                $char = mb_substr($filename, $i, 1, $encoding);

                if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) {
                    $filenameFallback .= '_';
                } else {
                    $filenameFallback .= $char;
                }
            }
        }

        $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback);
        $this->headers->set('Content-Disposition', $dispositionHeader);

        return $this;
    }

    /**
     * 根据请求信息处理头信息
     * @param Request $request
     * @return $this|Response
     */
    public function prepare(Request $request) {
        if (!$this->headers->has('Content-Type')) {
            $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream');
        }

        if ('HTTP/1.0' !== $request->server()->get('SERVER_PROTOCOL')) {
            $this->setProtocolVersion('1.1');
        }

        if (false === $fileSize = $this->file->getSize()) {
            return $this;
        }
        $this->headers->set('Content-Length', $fileSize);
        return $this;
    }

    /**
     * 发送文件内容
     * @return $this|Response
     */
    public function sendContent() {
        if (!$this->isSuccessful()) {
            return parent::sendContent();
        }

        $out = fopen('php://output', 'wb');
        $file = fopen($this->file->getPathname(), 'rb');

        stream_copy_to_stream($file, $out, $this->maxlen, $this->offset);

        fclose($out);
        fclose($file);

        if ($this->deleteFileAfterSend && file_exists($this->file->getPathname())) {
            unlink($this->file->getPathname());
        }

        return $this;
    }

    public function setContent($content) {
        if (null !== $content) {
            throw new \LogicException('二进制文件流的响应内容不能自定义.');
        }
    }

    public function getContent() {
        return false;
    }

    /**
     * 文件输出后直接删除
     * @param bool $shouldDelete
     * @return $this
     */
    public function deleteFileAfterSend($shouldDelete = true) {
        $this->deleteFileAfterSend = $shouldDelete;

        return $this;
    }
}